// ------------------------------------------------------------
// @Author: ZhangPeng
// @Date: 2024年12月16日22:38
// ------------------------------------------------------------

using System;
using UnityEngine;

public class MeshGenerator
{
    /// <summary>
    /// 顶点从左上角开始,往右侧,往下
    /// </summary>
    /// <param name="heightMap"></param>
    /// <param name="heightMultiplier"></param>
    /// <param name="heightCurve"></param>
    /// <param name="levelOfDetail"></param>
    /// <param name="meshHeightCurve"></param>
    /// <returns></returns>
    public static MeshData GenerateTerrainMesh(float[,] heightMap,MeshSettings meshSettings,  int levelOfDetail)
    {
        
        int meshSimplificationIncrement = levelOfDetail == 0 ? 1 : levelOfDetail * 2;
        //边界大小，即网格大小外一圈
        int borderedSize = heightMap.GetLength(0);
        //网格大小
        int meshSize = borderedSize - 2 * meshSimplificationIncrement;
        int meshUnSimplified = borderedSize - 2;

        //保证模型中心点为原点
        float topLeftX = (meshUnSimplified - 1) / -2f;
        float topLeftZ = (meshUnSimplified - 1) / 2f;


        int verticesPerLine = (meshSize - 1) / meshSimplificationIncrement + 1;

        MeshData meshData = new MeshData(verticesPerLine,meshSettings.useFlatShding);

        int[,] vertexIndicesMap = new int[borderedSize, borderedSize];
        int meshVertexIndex = 0;
        int borderVertexIndex = -1;

        for (int y = 0; y < borderedSize; y += meshSimplificationIncrement)
        {
            for (int x = 0; x < borderedSize; x += meshSimplificationIncrement)
            {
                bool isBorderVertex = y == 0 || y == borderedSize - 1 || x == 0 || x == borderedSize - 1;
                if (isBorderVertex)
                {
                    vertexIndicesMap[x, y] = borderVertexIndex;
                    borderVertexIndex--;
                }
                else
                {
                    vertexIndicesMap[x, y] = meshVertexIndex;
                    meshVertexIndex++;
                }
            }
        }

        //每个像素块需要绘制两个三角形
        for (int y = 0; y < borderedSize; y += meshSimplificationIncrement)
        {
            for (int x = 0; x < borderedSize; x += meshSimplificationIncrement)
            {
                int vertexIndex = vertexIndicesMap[x, y];
                Vector2 percent = new Vector2((x - meshSimplificationIncrement) / (float)meshSize, (y - meshSimplificationIncrement) / (float)meshSize);
                float height = heightMap[x, y];
                if (height <= 0)
                {
                    height = 0;
                }
                Vector3 vertexPosition = new Vector3((topLeftX + percent.x * meshUnSimplified)*meshSettings.meshScale, height, (topLeftZ - percent.y * meshUnSimplified)*meshSettings.meshScale); //从topLeftX往右，从topLeftZ往下

                meshData.AddVertex(vertexPosition, percent, vertexIndex);
                if (x < borderedSize - 1 && y < borderedSize - 1)
                {
                    int a = vertexIndicesMap[x, y];
                    int b = vertexIndicesMap[x + meshSimplificationIncrement, y];
                    int c = vertexIndicesMap[x, y + meshSimplificationIncrement];
                    int d = vertexIndicesMap[x + meshSimplificationIncrement, y + meshSimplificationIncrement];

                    meshData.AddTriangle(a, d, c);
                    meshData.AddTriangle(d, a, b);
                }
            }
        }
        meshData.Finalize();
        return meshData;
    }
}


public class MeshData
{
    private Vector3[] Vertices;
    private int[] Triangles;
    private Vector2[] UVs;
    private Vector3[] bakedNormals;

    private Vector3[] borderVertices;
    private int[] borderTriangles;

    private int triangleIndex;
    private int borderTriangleIndex;
    /// <summary>
    /// 使用平面着色,则不再共用三角形顶点
    /// </summary>
    private bool useFlatShading;

    public MeshData(int verticesPerLine,bool useFlatShading)
    {
        this.useFlatShading = useFlatShading;
        Vertices = new Vector3[verticesPerLine * verticesPerLine];
        UVs = new Vector2[verticesPerLine * verticesPerLine];
        Triangles = new int[(verticesPerLine - 1) * (verticesPerLine - 1) * 6];

        borderVertices = new Vector3[verticesPerLine * 4 + 4];
        borderTriangles = new int[24 * verticesPerLine];
    }

    public void AddVertex(Vector3 vertexPosition, Vector2 uv, int vertexIndex)
    {
        if (vertexIndex < 0)
        {
            borderVertices[-vertexIndex - 1] = vertexPosition;
        }
        else
        {
            Vertices[vertexIndex] = vertexPosition;
            UVs[vertexIndex] = uv;
        }
    }

    public void AddTriangle(int a, int b, int c)
    {
        if (a < 0 || b < 0 || c < 0)
        {
            borderTriangles[borderTriangleIndex] = a;
            borderTriangles[borderTriangleIndex + 1] = b;
            borderTriangles[borderTriangleIndex + 2] = c;
            borderTriangleIndex += 3;
        }
        else
        {
            Triangles[triangleIndex] = a;
            Triangles[triangleIndex + 1] = b;
            Triangles[triangleIndex + 2] = c;
            triangleIndex += 3;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    Vector3[] CalculateNormals()
    {
        Vector3[] vertexNormals = new Vector3[Vertices.Length];
        int triangleCount = Triangles.Length / 3;

        for (int i = 0; i < triangleCount; i++)
        {
            int normalTriangleIndex = i * 3;
            int vertexIndexA = Triangles[normalTriangleIndex];
            int vertexIndexB = Triangles[normalTriangleIndex + 1];
            int vertexIndexC = Triangles[normalTriangleIndex + 2];

            Vector3 triangleNormal = SurfaceNormalFormIndices(vertexIndexA, vertexIndexB, vertexIndexC);
            vertexNormals[vertexIndexA] += triangleNormal;
            vertexNormals[vertexIndexB] += triangleNormal;
            vertexNormals[vertexIndexC] += triangleNormal;
        }

        int borderTriangleCount = borderTriangles.Length / 3;
        for (int i = 0; i < borderTriangleCount; i++)
        {
            int normalTriangleIndex = i * 3;
            int vertexIndexA = borderTriangles[normalTriangleIndex];
            int vertexIndexB = borderTriangles[normalTriangleIndex + 1];
            int vertexIndexC = borderTriangles[normalTriangleIndex + 2];

            Vector3 triangleNormal = SurfaceNormalFormIndices(vertexIndexA, vertexIndexB, vertexIndexC);
            if (vertexIndexA >= 0)
                vertexNormals[vertexIndexA] += triangleNormal;
            if (vertexIndexB >= 0)
                vertexNormals[vertexIndexB] += triangleNormal;
            if (vertexIndexC >= 0)
                vertexNormals[vertexIndexC] += triangleNormal;
        }

        for (int i = 0; i < vertexNormals.Length; i++)
        {
            vertexNormals[i].Normalize();
        }

        return vertexNormals;
    }

    /// <summary>
    /// 从顶点索引计算法线
    /// </summary>
    /// <param name="indexA"></param>
    /// <param name="indexB"></param>
    /// <param name="indexC"></param>
    /// <returns></returns>
    Vector3 SurfaceNormalFormIndices(int indexA, int indexB, int indexC)
    {
        Vector3 pointA = (indexA < 0) ? borderVertices[-indexA - 1] : Vertices[indexA];
        Vector3 pointB = (indexB < 0) ? borderVertices[-indexB - 1] : Vertices[indexB];
        Vector3 pointC = (indexC < 0) ? borderVertices[-indexC - 1] : Vertices[indexC];
        //叉积，注意叉乘顺序
        return Vector3.Cross(pointB - pointA, pointC - pointA).normalized;
    }

    public void Finalize()
    {
        if (useFlatShading)
        {
            FlatShading();
        }
        else
        {
            BakedNormals();
        }
    }
    
    
    /// <summary>
    /// 避免法线计算在主线程中
    /// </summary>
    public void BakedNormals()
    {
        bakedNormals = CalculateNormals();
    }

    /// <summary>
    /// 将三角形的索引点单独拉出来
    /// </summary>
    void FlatShading()
    {
        Vector3[] flatShadedVertices = new Vector3[Triangles.Length];
        Vector2[] flatShadedUvs = new Vector2[Triangles.Length];

        for (int i = 0; i < Triangles.Length; i++)
        {
            flatShadedVertices[i] = Vertices[Triangles[i]];
            flatShadedUvs[i] = UVs[Triangles[i]];
            Triangles[i] = i;
        }

        Vertices = flatShadedVertices;
        UVs = flatShadedUvs;
    }
    
    public Mesh CreateMesh()
    {
        Mesh mesh = new Mesh();
        mesh.vertices = Vertices;
        mesh.triangles = Triangles;
        mesh.uv = UVs;
        if (useFlatShading)
        {
            mesh.RecalculateNormals();
        }
        else
        {
            mesh.normals = bakedNormals;
        }


        return mesh;
    }
}